/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package edu.mit.csail.sdg.alloy4compiler.translator;

import java.io.IOException;
import java.io.RandomAccessFile;
import edu.mit.csail.sdg.alloy4.Util;
import kodkod.engine.satlab.SATFactory;
import kodkod.engine.satlab.SATSolver;

/** An implementation of SATSolver that dumps the CNF to a file and then throws an exception
 * (this code is adapted from ExternalSolver from Kodkod).
 */

final class WriteCNF implements SATSolver {

    /** This runtime exception is thrown when the CNF file has been written successfully. */
    public static final class WriteCNFCompleted extends RuntimeException {
        /** This ensures the class can be serialized reliably. */
        private static final long serialVersionUID = 0;
        /** This constructs a new WriteCNFCompleted exception. */
        public WriteCNFCompleted() { super("CNF written successfully."); }
    }

    /** This is the CNF file we are generating. */
    private final RandomAccessFile cnf;

    /** This buffers up the clauses we are writing to the CNF file, to avoid excessive I/O. */
    private final StringBuilder buffer;

    /** This is the buffer size. */
    private static final int capacity = 8192;

    /** The number of variables so far. */
    private int vars = 0;

    /** The number of clauses so far. */
    private int clauses = 0;

    /** Helper method that returns a factory for WriteCNF instances. */
    public static final SATFactory factory(final String filename) {
        return new SATFactory() {
            /** {@inheritDoc} */
            @Override public SATSolver instance() { return new WriteCNF(filename); }
            /** {@inheritDoc} */
            @Override public boolean incremental() { return false; }
        };
    }

    /** Constructs a WriteCNF solver that will write CNF into the given file, without solving it. */
    private WriteCNF(String filename) {
        try {
            this.cnf = new RandomAccessFile(filename, "rw");
            this.cnf.setLength(0);
            this.buffer = new StringBuilder(capacity);
            for(int i = String.valueOf(Integer.MAX_VALUE).length()*2 + 8; i > 0; i--) {
                // get enough space into the buffer for the cnf header, which will be written last
                buffer.append(' ');
            }
            buffer.append('\n');
        } catch (Exception ex) {
            throw new RuntimeException("WriteCNF failed.", ex);
        }
    }

    /** Helper method that flushes the buffer. */
    private void flush() {
        try {
            cnf.writeBytes(buffer.toString());
            buffer.setLength(0);
        } catch (IOException ex) {
            throw new RuntimeException("WriteCNF failed.", ex);
        }
    }

    /** {@inheritDoc} */
    @Override protected void finalize() throws Throwable {
        super.finalize();
        free();
    }

    /** {@inheritDoc} */
    public void free() { Util.close(cnf); }

    /** {@inheritDoc} */
    public void addVariables(int numVars) {  if (numVars >= 0) vars += numVars; }

    /** {@inheritDoc} */
    public boolean addClause(int[] lits) {
        if (lits.length>0) {
            clauses++;
            if (buffer.length() > capacity) flush();
            for(int i=0; i<lits.length; i++) buffer.append(lits[i]).append(' ');
            buffer.append("0\n");
            return true;
        }
        return false;
    }

    /** {@inheritDoc} */
    public int numberOfVariables() { return vars; }

    /** {@inheritDoc} */
    public int numberOfClauses() { return clauses; }

    /** {@inheritDoc} */
    public boolean solve() {
        try {
            flush();
            cnf.seek(0);
            cnf.writeBytes("p cnf " + vars + " " + clauses);
            cnf.close();
        } catch (Exception ex) {
            throw new RuntimeException("WriteCNF failed.", ex);
        }
        throw new WriteCNFCompleted();
    }

    /** {@inheritDoc} */
    public boolean valueOf(int variable) { throw new IllegalStateException("This solver just writes the CNF without solving them."); }
}
